/*
Copyright (c) 2008 ZgFlex Contributors
License type: Mozilla Public License 1.1

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

package org.zgflex.desktop
{
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import mx.collections.ArrayCollection;
	import mx.containers.Canvas;
	import mx.core.ScrollPolicy;
	import mx.effects.Effect;
	import mx.effects.IEffectInstance;
	import mx.events.EffectEvent;
	import mx.events.ResizeEvent;
	import mx.managers.IFocusManagerComponent;
	
	import org.zgflex.desktop.layout.GridLayoutManager;
	import org.zgflex.desktop.layout.ILayoutManager;
	import org.zgflex.desktop.selection.ISelectionArea;
	import org.zgflex.desktop.selection.ISelectionRenderer;
	import org.zgflex.desktop.selection.SquareSelectionArea;
	import org.zgflex.desktop.selection.SquareSelectionRenderer;
	
	/**
	* Dispatched when the background is clicked.
	* @eventType org.zgflex.desktop.DesktopEvent
	* 
	*/	
	[Event(name="backgroundClick", type="org.zgflex.desktop.DesktopEvent")]
	
	/**
	* Dispatched when the background is double-clicked.
	* @eventType org.zgflex.desktop.DesktopEvent
	* 
	*/
	[Event(name="backgroundDoubleClick", type="org.zgflex.desktop.DesktopEvent")]
	
	/**
	* Dispatched when the icon is clicked.
	* @eventType org.zgflex.desktop.DesktopEvent
	* 
	*/
	[Event(name="iconClick", type="org.zgflex.desktop.DesktopEvent")]
	
	/**
	* Dispatched when the icon is double-clicked.
	* @eventType org.zgflex.desktop.DesktopEvent
	* 
	*/
	[Event(name="iconDoubleClick", type="org.zgflex.desktop.DesktopEvent")]
	
	/**
	* Dispatched when the icon is added.
	* @eventType org.zgflex.desktop.DesktopEvent
	* 
	*/
	[Event(name="iconAdded", type="org.zgflex.desktop.DesktopEvent")]
	
	/**
	* Dispatched when the icon is removed.
	* @eventType org.zgflex.desktop.DesktopEvent
	* 
	*/
	[Event(name="iconRemoved", type="org.zgflex.desktop.DesktopEvent")]
	
	/**
	* Dispatched when the iconManager is set.
	* @eventType org.zgflex.desktop.DesktopEvent
	* 
	*/
	[Event(name="layoutManagerSet", type="org.zgflex.desktop.DesktopEvent")]
	
	/**
	* Dispatched when layout starts updating.
	* @eventType org.zgflex.desktop.DesktopEvent
	* 
	*/
	[Event(name="layoutUpdate", type="org.zgflex.desktop.DesktopEvent")]
	
	/**
	* Dispatched when layout finishes updating.
	* @eventType org.zgflex.desktop.DesktopEvent
	* 
	*/
	[Event(name="layoutUpdated", type="org.zgflex.desktop.DesktopEvent")]
	
	/**
	 * A class for handling icons. It receives an array of icons. Also you can do 'live' operations like adding and removing icons etc.<br/> 
	 * Use a layout manager for layout operations (like snap to grid etc.).<br/>
	 * Use a selection manager for multi-select operations.
	 * @author Danko Kozar
	 * @see http://dankokozar.com Author&#39;s homepage
	 * 
	 */	
	public class Desktop extends Canvas implements IIconContainer, IIconSelector, IFocusManagerComponent
	{
		// selected icon attributes
		/**
		 * Background color of the selected icon.
		 */		
		public var selectedIconBackgroundColor:uint = 0xff66ff
		
		/**
		 * Background alpha of the selected icon.
		 */		
		public var selectedIconBackgroundAlpha:Number = 0.8;
		
		/**
		 * Border color of the selected icon.
		 */	
		public var selectedIconBorderColor:uint = 0xffff00;
		
		/**
		 * Border alpha of the selected icon.
		 */	
		public var selectedIconBorderAlpha:Number = 0.8;
		
		/**
		 * Border width of the selected icon.
		 */	
		public var selectedIconBorderWidth:uint = 4;
		
		// selection area
		/**
		 * Background color of the selection area.
		 */	
		public var selectionAreaBackgroundColor:uint = 0x0066ff;
		
		/**
		 * Background alpha of the selection area.
		 */	
		public var selectionAreaBackgroundAlpha:Number = 0.5;
		
		/**
		 * Border color of the selection area.
		 */	
		public var selectionAreaBorderColor:uint = 0xffff00;
		
		/**
		 * Background alpha of the selection area.
		 */	
		public var selectionAreaBorderAlpha:Number = 1;
		
		/**
		 * Background width of the selection area.
		 */	
		public var selectionAreaBorderWidth:uint = 2;
		
		/**
		 * Enables selection.
		 */	
		public var enableSelect:Boolean = true;
		
		/**
		 * Enables icon dragging.
		 */	
		public var enableDrag:Boolean = true;
		
		/**
		 * Process icon double-click events.
		 */	
		public var enableDoubleClick:Boolean = true;
		
		/**
		 * Run layouting after icon move.
		 */	
		public var layoutAfterMove:Boolean = false;
		
		/**
		 * The effect that plays when icon is added.
		 * @see mx.effects.Effect
		 */	
		public var iconAddedEffect:Effect;
		
		/**
		 * The effect that plays when icon is removed.
		 * @see mx.effects.Effect
		 */	
		public var iconRemovedEffect:Effect;
		
		public static const ICON:String = "ICON";
	    public static const BACKGROUND:String = "BACKGROUND";
	    /**
	     * @private 
	     */	    
	    protected var _clickedObject:String = BACKGROUND;
	    
	    // surfaces
	    /**
	     * A surface for drawing icon selections.
	     * @private
	     */	    
	    protected var _backSurface:Canvas;
	    
	    /**
	     * A surface for placing icons.
	     * @private
	     */	    
	    protected var _iconSurface:Canvas;
	    
	    /**
	     * A surface for drawing a selection area.
	     * @private
	     */	    
	    protected var _frontSurface:Canvas;
	    
	    /**
	     * A flag which indicating that the mouse is moved after mouseDown.
	     * @private
	     */	    
	    protected var _moved:Boolean = false;
	    
	    /**
	     * A flag which indicating selection mode after background mouse down.
	     * (without this the background click event fires and deselects all icons)
	     * @private
	     */	  
	    protected var _selectionMode:Boolean = false;
	    
	    ///////////////////////////////////////////////////////////////////////////////
		// Collections
		///////////////////////////////////////////////////////////////////////////////
	    
	    private var bIconsSet:Boolean = false;
	    /**
	     * @private 
	     */	
	    protected var _iconArray:Array;
	    /**
	     * @private 
	     */	
	    protected var _icons:ArrayCollection = new ArrayCollection();
	    /**
	     * Sets icons 
	     * @param value One icon of array of icons (DisplayObjects), usually set through MXML.
	     * @example The following code creates a Desktop with two images:

<listing version="3.0">
var desktop:Desktop = new Desktop();
desktop.percentWidth = 100;
desktop.percentHeight = 100;

var icons:Array = new Array();

var image1:Image = new Image();
image1.source = "some source";
image1.x = 100;
image1.y = 100;
icons.push(image1);

var image2:Image = new Image();
image2.source = "some other source";
image2.x = 200;
image2.y = 100;
icons.push(image2);

desktop.icons = icons; </listing>
	     */	    
	    public function set icons(value:Object):void
	    {
	    	// remove all icons first (clears the collection by itself)
	    	removeAllIcons();
	    	
	    	// clear selected icons collection
	    	_selectedIcons.removeAll();
	    	
	    	// filling the _icons collection
	    	if (value is Array){ // value is array of DisplayObjects
	    		_iconArray = value as Array;
	    	}
	    	else if (value is Object){ // value is one DisplayObject
	    		_iconArray = new Array();
	    		_iconArray.push(value);
	    	}
	
			// invalidate display list so the display is redrawn
	        bIconsSet = true;
	        invalidateDisplayList();
	    }
	    
	    /**
	     * Gets the icons iterator.
	     * @return 
	     * 
	     */
	    public function get iconsIterator():ArrayIterator {
	    	return new ArrayIterator(_icons.toArray());
	    }
	    
	    /**
	     * Icons that are currently selected.
	     * @private 
	     */	
	    protected var _selectedIcons:ArrayCollection = new ArrayCollection();
	    /**
	     * Gets the selected icons iterator.
	     * @return Iterator
	     * 
	     */
	    public function get selectedIconsIterator():ArrayIterator {
	    	return new ArrayIterator(_selectedIcons.toArray());
	    }
	    
	    ///////////////////////////////////////////////////////////////////////////////
		// Layout
		///////////////////////////////////////////////////////////////////////////////
	    
	    /**
	     * @private 
	     */	
	    protected var _layoutManager:ILayoutManager;
	    /**
	     * Layout manager.
	     * @see org.zgflex.desktop.layout.ILayoutManager
	     * @see org.zgflex.desktop.layout.LayoutManagerBase
	     * 
	     */
     	public function get layoutManager():ILayoutManager  {
	    	if (_layoutManager == null)
	    		throw new DesktopError(DesktopError.LAYOUT_MANAGER_IS_NULL);
	    	return _layoutManager;
	    } 
	    /**
	     * @param value A reference to layout manager.
	     * 
	     */
	    public function set layoutManager(value:ILayoutManager):void  {
	    	if (value == null)
	    		throw new DesktopError(DesktopError.LAYOUT_MANAGER_AREA_CANNOT_SET_TO_NULL);

	    	_layoutManager = value;
	    	_layoutManager.target = this;
	    	_layoutManager.addEventListener("layoutChanged", layoutChangedHandler);
	    }
	    
	    /**
	     * Runs a layout update on layoutManager.
	     * @param config
	     * 
	     */	    
	    public function doLayout(config:Object = null):void {
	    	dispatchEvent(new DesktopEvent("layoutUpdate"));
	    	layoutManager.doLayout(config);
	    }
	   	
	   	///////////////////////////////////////////////////////////////////////////////
		// Selection
		///////////////////////////////////////////////////////////////////////////////
	    /**
	     * @private 
	     */	
	    protected var _selectionArea:ISelectionArea;
	    /**
	     * Selection area</br>
	     * Renders a rectangle-like area when clicking a background and dragging for icon multi-selection.
	     * @see org.zgflex.desktop.selection.ISelectionArea
	     * @see org.zgflex.desktop.selection.SelectionAreaBase
	     * 
	     */
	    public function get selectionArea():ISelectionArea  {
	    	if (_selectionArea == null)
	    		throw new DesktopError(DesktopError.SELECTION_AREA_IS_NULL);
	    	return _selectionArea;
	    }  
	    public function set selectionArea(value:ISelectionArea):void  {
	    	if (value == null)
	    		throw new DesktopError(DesktopError.SELECTION_AREA_CANNOT_SET_TO_NULL);
	    	_selectionArea = value;
	    	_selectionArea.target = this;
	    	_selectionArea.surface = _frontSurface;
	    }
	    
	    /**
	     * @private 
	     */	
	    protected var _selectionRenderer:ISelectionRenderer;
	    /**
	     * Selection renderer</br>
	     * Renders a rectangle behing each selected icon.
	     * @see org.zgflex.desktop.selection.ISelectionRenderer
	     * @see org.zgflex.desktop.selection.SelectionRendererBase
	     * 
	     */
	    public function get selectionRenderer():ISelectionRenderer  {
	    	if (_selectionRenderer == null)
	    		throw new DesktopError(DesktopError.SELECTION_RENDERER_IS_NULL);
	    	return _selectionRenderer;
	    }
	    public function set selectionRenderer(value:ISelectionRenderer):void  {
	    	if (value == null)
	    		throw new DesktopError(DesktopError.SELECTION_RENDERER_CANNOT_SET_TO_NULL);
	    	_selectionRenderer = value;
	    	_selectionRenderer.target = this;
	    	_selectionRenderer.surface = _backSurface;
	    }	    
	    
	    /**
	     * Last click coordinates (local coordinates).
	     * @private
	     */	    
	    protected var _lastClickPosition:Point;
		
		/**
		 * Constructor.
		 * 
		 */		
		public function Desktop()
		{
			doubleClickEnabled = true;
			horizontalScrollPolicy = ScrollPolicy.OFF;
			verticalScrollPolicy = ScrollPolicy.OFF;
			
			addEventListener(MouseEvent.MOUSE_DOWN, backgroundMouseDownHandler);			
			addEventListener(MouseEvent.CLICK, backgroundClickHandler);
			addEventListener(MouseEvent.DOUBLE_CLICK, backgroundDoubleClickHandler);
			addEventListener(ResizeEvent.RESIZE, desktopResizeHandler);
		}

		///////////////////////////////////////////////////////////////////////////////
		// Icon operations
		///////////////////////////////////////////////////////////////////////////////

		/**
		 * @inheritDoc 
		 * 
		 */		
		public function addIcon(icon:DisplayObject):void {
			//icon.doubleClickEnabled = true;
			addIconListeners(icon);
			if (iconAddedEffect){
				iconAddedEffect.createInstance(icon).startEffect();
			}
			_icons.addItem(icon);
			if (!_iconSurface.contains(icon))
				_iconSurface.addChild(icon);
			
			dispatchEvent(new DesktopEvent("iconAdded"));
		}
		
		/**
		 * @inheritDoc 
		 * 
		 */			
		public function removeIcon(icon:DisplayObject):void {
			var index:int = _icons.getItemIndex(icon);
			if (index > -1){
				removeIconListeners(icon);
				dispatchEvent(new DesktopEvent(DesktopEvent.ICON_REMOVED));
				removeIconFromList(icon);
				removeIconFromDisplayList(icon);
				dispatchEvent(new DesktopEvent("iconRemoved"));
			} else {
				throw new DesktopError(DesktopError.ICON_NOT_FOUND_IN_THE_COLLECTION);
			}
		}
		
		/**
		 * @inheritDoc 
		 * 
		 */			
		public function removeAllIcons():void {
			for each (var icon:DisplayObject in _icons){
				removeIconListeners(icon);
				removeIconFromDisplayList(icon);							
			}
			_icons.removeAll();
			updateSelection();
		}
		
		/**
		 * @inheritDoc 
		 * 
		 */		
		public function selectIcon(icon:DisplayObject):void {
			if (!_selectedIcons.contains(icon)){
				_selectedIcons.addItem(icon);
				updateSelection();			
			}
		}
		
		/**
		 * @inheritDoc 
		 * 
		 */			
		public function deselectIcon(icon:DisplayObject):void {
			var index:int = _selectedIcons.getItemIndex(icon);
			if (index > -1){
				_selectedIcons.removeItemAt(index);
				//icon.graphics.clear();			
			}
		}
		
		/**
		 * @inheritDoc 
		 * 
		 */		
		public function toggleIcon(icon:DisplayObject):void {
			var index:int = _selectedIcons.getItemIndex(icon);
			if (index > -1)	// icon is selected		
				deselectIcon(icon)
			else
				selectIcon(icon);
		}
		
		/**
		 * @inheritDoc 
		 * 
		 */		
		public function deselectAllIcons():void {
			var dummy:Number = 1;
			for each (var icon:DisplayObject in _selectedIcons){
				//icon.graphics.clear();
			}
			_selectedIcons.removeAll();
			updateSelection();
		}
		
		/**
		 * @inheritDoc 
		 * 
		 */	
		public function bringToFront(icon:DisplayObject):void {
			if (_icons.contains(icon))
				_iconSurface.setChildIndex(icon, _iconSurface.getChildren().length - 1);
		}
		
		/**
		 * Removes the icon from a display list.<br/>
		 * If iconRemovedEffect is set on a Desktop, this effect starts on the icon.<br/>
		 * On effectEnd event the icon is removed from a display list.<br/>
		 * If iconRemovedEffect is not set on the Desktop, it is removed immediatelly.
		 * @param icon
		 * @private
		 * 
		 */		
		protected function removeIconFromDisplayList(icon:DisplayObject):void {
			if (icon.parent == _iconSurface){ // icon can exist in the collection, but isn't neccessary displayed, for instance because of the remove effect 
				if (iconRemovedEffect){
					var instance:IEffectInstance = iconRemovedEffect.createInstance(icon);
					instance.startEffect();
					iconRemovedEffect.addEventListener(EffectEvent.EFFECT_END, iconRemovedEffectEndHandler);
				} else {
					_iconSurface.removeChild(icon);
					updateSelection();
				}
			}	
		}
		
		/**
		 * Removes icon from the _icons and _selectedIcons collections, but not from a display list.<br/>
		 * After removal, the layout operations will not work on this icon anymore.
		 * @param icon
		 * @private
		 * 
		 */		
		protected function removeIconFromList(icon:DisplayObject):void {
			var index:int;
			index = _icons.getItemIndex(icon);
			if (index > -1){
				_icons.removeItemAt(index);
			}
			index = _selectedIcons.getItemIndex(icon);
			if (index > -1){
				_selectedIcons.removeItemAt(index);
			}
		}
		
		/**
		 * The function which is called on the EFFECT_END on removing the icon.
		 * @param e
		 * 
		 */		
		protected function iconRemovedEffectEndHandler(e:EffectEvent):void {
			var icon:DisplayObject = e.effectInstance.target as DisplayObject;
			if (icon.parent == _iconSurface && _iconSurface.contains(icon)){
				_iconSurface.removeChild(icon);
				updateSelection();
			}
		}
		
		///////////////////////////////////////////////////////////////////////////////
		// Icon listeners
		///////////////////////////////////////////////////////////////////////////////
		
		/**
		 * Adds event listeners to the icon.
		 * @param icon The icon to add listeners to
		 * 
		 */		
		protected function addIconListeners(icon:DisplayObject):void {
			icon.addEventListener(MouseEvent.MOUSE_DOWN, iconMouseDownHandler);
			icon.addEventListener(MouseEvent.CLICK, iconClickHandler);
			icon.addEventListener(MouseEvent.DOUBLE_CLICK, iconDoubleClickHandler);
		}
		
		/**
		 * Removes icon event listeners.
		 * @param icon The icon to remove listeners from
		 * 
		 */		
		protected function removeIconListeners(icon:DisplayObject):void {
			icon.removeEventListener(MouseEvent.CLICK, iconClickHandler);
			icon.removeEventListener(MouseEvent.DOUBLE_CLICK, iconDoubleClickHandler);
			icon.removeEventListener(MouseEvent.MOUSE_DOWN, iconMouseDownHandler);
		}
		
		///////////////////////////////////////////////////////////////////////////////
		// Mouse event handlers
		///////////////////////////////////////////////////////////////////////////////
		
		/**
		 * Handles a mouse up.
		 * @param e
		 * 
		 */		
		protected function mouseUpHandler(e:MouseEvent):void {
			e.stopPropagation();
			trace("mouseUpHandler");
			if (_clickedObject == BACKGROUND){
				if (enableSelect && _selectionArea)
					selectionArea.stop();
			} else if (_clickedObject == ICON && layoutAfterMove && _layoutManager) {
				doLayout();
			}
			_moved = false;
			_lastClickPosition = null;
			systemManager.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
			systemManager.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
			stage.removeEventListener(Event.MOUSE_LEAVE, mouseLeaveHandler);
		}
		
		/**
		 * Handles a mouse leave.
		 * @param e
		 * 
		 */		
		protected function mouseLeaveHandler(e:Event):void {
			e.stopPropagation();
			trace("mouseLeaveHandler");
			/* _mouseDown = false; */
			if (_clickedObject == BACKGROUND){
				if (enableSelect && _selectionArea)
					selectionArea.stop();
			} else if (_clickedObject == ICON && layoutAfterMove && _layoutManager) {
				doLayout();
			}
			_moved = false;
			_lastClickPosition = null;
			_selectionMode = false;
			systemManager.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
			systemManager.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
			stage.removeEventListener(Event.MOUSE_LEAVE, mouseLeaveHandler); // only stage dispatches a mouseLeave event
		}
		
		/**
		 * Handles a mouse move.<br/>
		 * This method is handling mouse moves after both icon click or background click.
		 * @param e
		 * 
		 */		
		protected function mouseMoveHandler(e:MouseEvent):void {
			 if (!_lastClickPosition)
			 	return;
			 
 			 // we have to calculate using the global coordinates
			 // because we can drag over other elements (icons, elements outside desktop on mouse leave), and we need global mouse coordinates
			 var newPositionGlobal:Point = new Point(e.stageX, e.stageY);
			 var newPositionLocal:Point = this.globalToLocal(newPositionGlobal);
			 var moveVector:Point = newPositionLocal.subtract(_lastClickPosition);
			 //trace("moving icon(s): lastPosition"+_lastClickPosition+" newPosition="+newPositionLocal+" moveVector="+moveVector);
					
			 switch (_clickedObject){
			 	case ICON:			 	
			 		 // do it only if target is desktop (not icon)
					for each (var icon:DisplayObject in _selectedIcons){
						icon.x += moveVector.x;
						icon.y += moveVector.y;
					}				
					_lastClickPosition = newPositionLocal;
					break;
			 	
			 	case BACKGROUND:			 		
			 		if (!_moved){
			 			_selectionMode = true;
			 			if (!e.ctrlKey && !e.shiftKey) 
		 					deselectAllIcons();
			 		}
					if (enableSelect && _selectionArea)				
			 			selectionArea.move(newPositionLocal, e.ctrlKey);
			 		break;
			 	
			 	default:
			 		break;			 
			 }
			 
			 updateSelection();
			 _moved = true;			 
			 		
		}	
		
		///////////////////////////////////////////////////////////////////////////////
		// Icon event handlers
		///////////////////////////////////////////////////////////////////////////////
		
		/**
		 * Handles the icon click.
		 * @param e
		 * 
		 */		
		protected function iconClickHandler(e:MouseEvent):void {
			if (!_icons.contains(e.currentTarget))
				return;
			// hold the click event and don't propagate it to the background
			e.stopPropagation();
			trace("iconClickHandler");
			var event:DesktopEvent = new DesktopEvent(DesktopEvent.ICON_CLICK);
			event.icon = e.currentTarget as DisplayObject;
			event.mouseEvent = e;
			dispatchEvent(event);
		}
		
		/**
		 * Handles the icon double-click. 
		 * @param e
		 * 
		 */		
		protected function iconDoubleClickHandler(e:MouseEvent):void {
			if (!_icons.contains(e.currentTarget))
				return;
			e.stopPropagation();
			trace("iconDoubleClickHandler");
			// redispatch the event if target is icon, not desktop
			if (e.target != _iconSurface){
				var event:DesktopEvent = new DesktopEvent(DesktopEvent.ICON_DOUBLE_CLICK);
				event.icon = e.currentTarget as DisplayObject;
				event.mouseEvent = e;
				dispatchEvent(event);
			}
		}
		
		/**
		 * Handles the icon mouse down. 
		 * @param e
		 * 
		 */		
		protected function iconMouseDownHandler(e:MouseEvent):void {
			if (!_icons.contains(e.currentTarget))
				return;
			
			trace("iconMouseDownHandler");
			e.stopPropagation();
			
			_clickedObject = ICON;
			
			var positionGlobal:Point = new Point(e.stageX, e.stageY);
			_lastClickPosition = this.globalToLocal(positionGlobal);
			
			var icon:DisplayObject = DisplayObject(e.currentTarget);
			
			if (enableSelect){
				// if clicking an unselected icon without holding ctrl or shift - deselect all other icons
				if (!e.ctrlKey && !e.shiftKey && !_selectedIcons.contains(icon)){ 
					deselectAllIcons();
				}
				// if holding ctrl or shift - toggle selection
				if (e.ctrlKey && !e.shiftKey)
					toggleIcon(icon);
				else
					selectIcon(icon);
					
				bringToFront(icon);
			}
			
			if (enableDrag){
				systemManager.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false); // useCapture=false!
				systemManager.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
				stage.addEventListener(Event.MOUSE_LEAVE, mouseLeaveHandler);
			}
		}
		
		///////////////////////////////////////////////////////////////////////////////
		// Background event handlers
		///////////////////////////////////////////////////////////////////////////////
				
		/**
		 * Handles a background mouse down event.
		 * @param e
		 * A mouse event
		 */		
		protected function backgroundMouseDownHandler(e:MouseEvent):void {
			if (e.target == this){
				_clickedObject = BACKGROUND;
				trace('background mouse down');
				
				systemManager.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler, false); // useCapture=false!
				systemManager.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
				stage.addEventListener(Event.MOUSE_LEAVE, mouseLeaveHandler);
				
				_lastClickPosition = new Point(e.localX, e.localY);
				if (enableSelect && _selectionArea)
					selectionArea.start(_lastClickPosition);
			}
		}		
		
		/**
		 * Handles a background click event. 
		 * @param e
		 * A mouse event
		 */		
		protected function backgroundClickHandler(e:MouseEvent):void {
			if (e.target == this){
				if (_selectionMode)
	 				_selectionMode = false; // reset selection mode
	 			else {
					trace('background click');
					deselectAllIcons();
					
					var event:DesktopEvent = new DesktopEvent(DesktopEvent.BACKGROUND_CLICK);
					event.icon = e.target as DisplayObject;
					event.mouseEvent = e;
					dispatchEvent(event);
	 			}				
			}
		}
		
		/**
		 * Handles a background double-click event.
		 * @param e A mouse event
		 * 
		 */		
		protected function backgroundDoubleClickHandler(e:MouseEvent):void {
			if (e.target == this){
				trace('background doubleClick');
				//deselectAllIcons();
				var event:DesktopEvent = new DesktopEvent(DesktopEvent.BACKGROUND_DOUBLE_CLICK);
				event.icon = e.target as DisplayObject;
				event.mouseEvent = e;
				dispatchEvent(event);
			}
		}
		
		///////////////////////////////////////////////////////////////////////////////
		// Desktop event handlers
		///////////////////////////////////////////////////////////////////////////////
		
		/**
		 * Handles a desktop resize event.
		 * @param e Resize event
		 * 
		 */		
		protected function desktopResizeHandler(e:ResizeEvent):void {
			trace('desktop resize');
			doLayout();
		}
		
		/**
		 * Handles a layout changed event.
		 * @param e
		 * 
		 */		
		protected function layoutChangedHandler(e:Event):void {
			trace('layout changed');
			updateSelection();
		}
		
		///////////////////////////////////////////////////////////////////////////////
		// Desktop routines
		///////////////////////////////////////////////////////////////////////////////
		
		/**
		 * @inheritDoc 
		 * 
		 */	
		public function getContainerDimensions():Rectangle {
			return this.getBounds(this);
		} 
		
		/**
		 * @inheritDoc 
		 * 
		 */		
		override protected function createChildren():void {
			super.createChildren();
			
			// create default layout manager
			_layoutManager = new GridLayoutManager(this);
			
			// back surface (for placing icon selections)
			if (enableSelect){
				// surface
				_backSurface = new Canvas();
				_backSurface.percentWidth = 100;
				_backSurface.percentHeight = 100;
				_backSurface.horizontalScrollPolicy = ScrollPolicy.OFF;
				_backSurface.verticalScrollPolicy = ScrollPolicy.OFF;
				addChild(_backSurface);
				
				// default selection renderer
				var sr:SquareSelectionRenderer = new SquareSelectionRenderer(this);
				sr.backgroundColor = selectedIconBackgroundColor;
				sr.backgroundAlpha = selectedIconBackgroundAlpha;
				sr.borderColor = selectedIconBorderColor;
				sr.borderAlpha = selectedIconBorderAlpha;
				sr.borderWidth = selectedIconBorderWidth;
				sr.surface = _backSurface;
				_selectionRenderer = sr;				
			}
			
			// icon surface
			_iconSurface = new Canvas();
			_iconSurface.percentWidth = 100;
			_iconSurface.percentHeight = 100;
			_iconSurface.horizontalScrollPolicy = ScrollPolicy.OFF;
			_iconSurface.verticalScrollPolicy = ScrollPolicy.OFF;
			addChild(_iconSurface);
			
			// front surface (for drawing a selection rectangle)
			if (enableSelect){
				// surface
				_frontSurface = new Canvas();
				_frontSurface.percentWidth = 100;
				_frontSurface.percentHeight = 100;
				_frontSurface.horizontalScrollPolicy = ScrollPolicy.OFF;
				_frontSurface.verticalScrollPolicy = ScrollPolicy.OFF;				
				addChild(_frontSurface);
				
				// default selection area
				var sa:SquareSelectionArea = new SquareSelectionArea(this);
				sa.backgroundColor = selectionAreaBackgroundColor;
				sa.backgroundAlpha = selectionAreaBackgroundAlpha;
				sa.borderColor = selectionAreaBorderColor;
				sa.borderAlpha = selectionAreaBorderAlpha;
				sa.borderWidth = selectionAreaBorderWidth;
				sa.surface = _frontSurface;
				_selectionArea = sa;				
			}
		}
		
		/**
		 * @inheritDoc 
		 * 
		 */	
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
			super.updateDisplayList(unscaledWidth, unscaledHeight);
			
			if (bIconsSet){
				bIconsSet = false;
				
				// filling the _icons collection and updating a display at the same time
	    		for each (var icon:DisplayObject in _iconArray){ // add all icons from array to _icons collection
	    			addIcon(icon);
	    		}
			}
		}
		
		/**
		 * @inheritDoc 
		 * 
		 */
		override public function setFocus():void {
			super.setFocus();
			//drawFocus(true);
		}
		
		/**
		 * @inheritDoc 
		 * 
		 */
		override public function drawFocus(isFocused:Boolean):void {
			super.drawFocus(isFocused);
			if (!_frontSurface) return;
			_frontSurface.graphics.clear();
			if (isFocused){				
				_frontSurface.graphics.lineStyle(3, 0x00ff66);
				_frontSurface.graphics.drawRect(0, 0, width, height);
			}
		}
		
		/**
		 * Commands the selection renderer to update selections
		 * 
		 */		
		protected function updateSelection():void {
			if (enableSelect && _selectionRenderer)
				_selectionRenderer.update();
		}
	}
}